 /*******************************************************************************
  * Copyright (c) 2004, 2007 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  * Daniel Megert daniel_megert@ch.ibm.com Bug 169696
  *******************************************************************************/
 package org.eclipse.ui.ide.dialogs;

 import java.nio.charset.Charset ;
 import java.nio.charset.IllegalCharsetNameException ;
 import java.util.List ;

 import org.eclipse.jface.preference.FieldEditor;
 import org.eclipse.jface.preference.IPreferenceStore;
 import org.eclipse.osgi.util.NLS;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.events.KeyAdapter;
 import org.eclipse.swt.events.KeyEvent;
 import org.eclipse.swt.events.SelectionAdapter;
 import org.eclipse.swt.events.SelectionEvent;
 import org.eclipse.swt.layout.GridData;
 import org.eclipse.swt.layout.GridLayout;
 import org.eclipse.swt.widgets.Button;
 import org.eclipse.swt.widgets.Combo;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Group;
 import org.eclipse.ui.WorkbenchEncoding;
 import org.eclipse.ui.ide.IDEEncoding;
 import org.eclipse.ui.internal.ide.IDEWorkbenchMessages;

 /**
  * The abstract superclass of field editors used to set an encoding. Any user
  * entered encodings will be added to the list of encodings available via
  * {@link org.eclipse.ui.ide.IDEEncoding}.
  * <p>
  * Subclasses may extend, but must call <code>createEncodingGroup</code>
  * during <code>doFillIntoGrid</code>.
  * </p>
  *
  * @see org.eclipse.ui.ide.IDEEncoding
  * @since 3.1
  */
 public abstract class AbstractEncodingFieldEditor extends FieldEditor {

     private Composite container;

     private Button defaultEncodingButton;

     private String defaultEnc;

     private Button otherEncodingButton;

     private Combo encodingCombo;

     private boolean isValid = true;

     private String oldSelectedEncoding;

     private String groupTitle = IDEWorkbenchMessages.WorkbenchPreference_encoding;

     /**
      * Creates a new encoding field editor with no settings set.
      */
     protected AbstractEncodingFieldEditor() {
         super();
     }

     /**
      * Creates a new encoding field editor with the given preference name, label
      * and parent.
      *
      * @param name
      * the name of the preference this field editor works on
      * @param labelText
      * the label text of the field editor
      * @param parent
      * the parent of the field editor's control
      */
     protected AbstractEncodingFieldEditor(String name, String labelText,
             Composite parent) {
         super(name, labelText, parent);
     }

     /**
      * Creates a new encoding field editor with the given preference name, label
      * and parent.
      *
      * @param name
      * the name of the preference this field editor works on
      * @param labelText
      * the label text of the field editor
      * @param groupTitle
      * the title for the field editor's control. If groupTitle is
      * <code>null</code> the control will be unlabelled
      * (by default a {@link Composite} instead of a {@link Group}.
      * @param parent
      * the parent of the field editor's control
      * @see AbstractEncodingFieldEditor#setGroupTitle(String)
      * @since 3.3
      */
     protected AbstractEncodingFieldEditor(String name, String labelText,
             String groupTitle, Composite parent) {
         init(name, labelText);
         this.groupTitle = groupTitle;
         createControl(parent);
     }

     /*
      * (non-Javadoc)
      *
      * @see org.eclipse.jface.preference.FieldEditor#adjustForNumColumns(int)
      */
     protected void adjustForNumColumns(int numColumns) {
         ((GridData) getContainer().getLayoutData()).horizontalSpan = numColumns;
     }

     /*
      * (non-Javadoc)
      *
      * @see org.eclipse.jface.preference.FieldEditor#doFillIntoGrid(org.eclipse.swt.widgets.Composite,
      * int)
      */
     protected void doFillIntoGrid(Composite parent, int numColumns) {
         container = createEncodingGroup(parent, numColumns);
     }

     /*
      * (non-Javadoc)
      *
      * @see org.eclipse.jface.preference.FieldEditor#doLoad()
      */
     protected void doLoad() {
         if (encodingCombo != null) {
             List encodings = IDEEncoding.getIDEEncodings();
             String resourcePreference = getStoredValue();
             populateEncodingsCombo(encodings, resourcePreference);
             updateEncodingState(resourcePreference == null);
         }
     }

     /**
      * Returns the value that is currently stored for the encoding.
      *
      * @return the currently stored encoding
      */
     protected abstract String getStoredValue();

     /*
      * (non-Javadoc)
      *
      * @see org.eclipse.jface.preference.FieldEditor#doLoadDefault()
      */
     protected void doLoadDefault() {
         updateEncodingState(true);
     }

     /*
      * (non-Javadoc)
      *
      * @see org.eclipse.jface.preference.FieldEditor#getNumberOfControls()
      */
     public int getNumberOfControls() {
         return 1;
     }

     /*
      * (non-Javadoc)
      *
      * @see org.eclipse.jface.preference.FieldEditor#isValid()
      */
     public boolean isValid() {
         return isValid;
     }

     /*
      * (non-Javadoc)
      *
      * @see org.eclipse.jface.preference.FieldEditor#refreshValidState()
      */
     protected void refreshValidState() {
         updateValidState();
     }

     /*
      * (non-Javadoc)
      *
      * @see org.eclipse.jface.preference.FieldEditor#setPreferenceStore(org.eclipse.jface.preference.IPreferenceStore)
      */
     public void setPreferenceStore(IPreferenceStore store) {
         super.setPreferenceStore(store);
         defaultEnc = store.getDefaultString(getPreferenceName());
         updateDefaultEncoding();
     }

     private void updateDefaultEncoding() {
         defaultEncodingButton.setText(defaultButtonText());
     }

     private Composite getContainer() {
         return container;
     }

     /**
      * Creates a composite with all the encoding controls.
      * <p>
      * Subclasses may extend.
      * </p>
      *
      * @param parent
      * the parent widget
      * @param numColumns
      * the number of columns in the parent
      * @return the group control
      */
     protected Composite createEncodingGroup(Composite parent, int numColumns) {

         Composite topControl;
         GridLayout layout = new GridLayout();
         layout.numColumns = 2;

         if (groupTitle == null){
             topControl = new Composite(parent, SWT.NONE);
             layout.marginWidth = 0;
             layout.marginHeight = 0;
         }
         else {
             Group top = new Group(parent, SWT.NONE);
             top.setText(groupTitle);
             topControl = top;
         }

         GridData data = new GridData(GridData.FILL_HORIZONTAL);
         topControl.setLayoutData(data);
         topControl.setLayout(layout);

         SelectionAdapter buttonListener = new SelectionAdapter() {
             public void widgetSelected(SelectionEvent e) {
                 updateEncodingState(defaultEncodingButton.getSelection());
                 updateValidState();
             }
         };

         defaultEncodingButton = new Button(topControl, SWT.RADIO);
         defaultEnc = findDefaultEncoding();
         defaultEncodingButton.setText(defaultButtonText());
         data = new GridData();
         data.horizontalSpan = 2;
         defaultEncodingButton.setLayoutData(data);
         defaultEncodingButton.addSelectionListener(buttonListener);

         otherEncodingButton = new Button(topControl, SWT.RADIO);
         otherEncodingButton
                 .setText(IDEWorkbenchMessages.WorkbenchPreference_otherEncoding);
         otherEncodingButton.addSelectionListener(buttonListener);

         encodingCombo = new Combo(topControl, SWT.NONE);
         data = new GridData();
         encodingCombo.setLayoutData(data);
         encodingCombo.addSelectionListener(new SelectionAdapter() {
             /*
              * (non-Javadoc)
              *
              * @see org.eclipse.swt.events.SelectionListener#widgetSelected(org.eclipse.swt.events.SelectionEvent)
              */
             public void widgetSelected(SelectionEvent e) {
                 updateValidState();
             }
         });
         encodingCombo.addKeyListener(new KeyAdapter() {
             /*
              * (non-Javadoc)
              *
              * @see org.eclipse.swt.events.KeyListener#keyReleased(org.eclipse.swt.events.KeyEvent)
              */
             public void keyReleased(KeyEvent e) {
                 updateValidState();
             }
         });

         return topControl;
     }

     /*
      * (non-Javadoc)
      *
      * @see org.eclipse.jface.preference.FieldEditor#setEnabled(boolean,
      * org.eclipse.swt.widgets.Composite)
      * @since 3.3
      */
     public void setEnabled(boolean enabled, Composite parent) {
         if (container != null)
             container.setEnabled(enabled);
         if (defaultEncodingButton != null)
             defaultEncodingButton.setEnabled(enabled);
         if (otherEncodingButton != null)
             otherEncodingButton.setEnabled(enabled);
         if (encodingCombo != null)
             encodingCombo.setEnabled(enabled);
         
     }

     /**
      * Returns the default encoding for the object being shown.
      *
      * @return the default encoding for the object being shown
      */
     protected String findDefaultEncoding() {
         return WorkbenchEncoding.getWorkbenchDefaultEncoding();
     }

     /**
      * Returns the text for the default encoding button.
      *
      * @return the text for the default encoding button
      */
     protected String defaultButtonText() {
         return NLS.bind(
                 IDEWorkbenchMessages.WorkbenchPreference_defaultEncoding,
                 defaultEnc);
     }

     /**
      * Populates the encodings combo. Sets the text based on the selected
      * encoding. If there is no selected encoding, the text is set to the
      * default encoding.
      *
      * @param encodings
      * the list of encodings (list of String)
      * @param selectedEncoding
      * the selected encoding, or <code>null</code>
      */
     private void populateEncodingsCombo(List encodings, String selectedEncoding) {
         String [] encodingStrings = new String [encodings.size()];
         encodings.toArray(encodingStrings);
         encodingCombo.setItems(encodingStrings);

         if (selectedEncoding == null) {
             encodingCombo.setText(getDefaultEnc());
         } else {
             encodingCombo.setText(selectedEncoding);
         }
     }

     private void updateEncodingState(boolean useDefault) {
         defaultEncodingButton.setSelection(useDefault);
         otherEncodingButton.setSelection(!useDefault);
         if (useDefault) {
             encodingCombo.setText(getDefaultEnc());
         }
         encodingCombo.setEnabled(!useDefault);
         setPresentsDefaultValue(useDefault);
         updateValidState();
     }

     private void updateValidState() {
         boolean isValidNow = isEncodingValid();
         if (isValidNow != isValid) {
             isValid = isValidNow;
             if (isValid) {
                 clearErrorMessage();
             } else {
                 showErrorMessage(IDEWorkbenchMessages.WorkbenchPreference_unsupportedEncoding);
             }
             fireStateChanged(IS_VALID, !isValid, isValid);
         }
         String newValue = getSelectedEncoding();
         if (isValid && !newValue.equals(oldSelectedEncoding)) {
             fireValueChanged(VALUE, oldSelectedEncoding, newValue);
             oldSelectedEncoding = newValue;
         }
     }

     /**
      * Returns the currently selected encoding.
      *
      * @return the currently selected encoding
      */
     protected String getSelectedEncoding() {
         if (defaultEncodingButton.getSelection()) {
             return defaultEnc;
         }
         return encodingCombo.getText();
     }

     private boolean isEncodingValid() {
         return defaultEncodingButton.getSelection()
                 || isValidEncoding(encodingCombo.getText());
     }

     /**
      * Returns whether or not the given encoding is valid.
      *
      * @param enc
      * the encoding to validate
      * @return <code>true</code> if the encoding is valid, <code>false</code>
      * otherwise
      */
     private boolean isValidEncoding(String enc) {
         try {
             return Charset.isSupported(enc);
         } catch (IllegalCharsetNameException e) {
             // This is a valid exception
 return false;
         }

     }

     /**
      * Returns the default encoding.
      *
      * @return the default encoding
      */
     protected String getDefaultEnc() {
         return defaultEnc;
     }

     /**
      * Returns whether or not the encoding setting changed.
      *
      * @param encodingSetting
      * the setting from the page.
      * @return boolean <code>true</code> if the resource encoding is the same
      * as before.
      */
     protected boolean hasSameEncoding(String encodingSetting) {

         String current = getStoredValue();

         if (encodingSetting == null) {
             // Changed if default is selected and there is no setting
 return current == null || current.length() == 0;
         }
         return encodingSetting.equals(current);
     }

     /**
      * Return whether or not the default has been selected.
      *
      * @return <code>true</code> if the default button has been selected.
      */
     boolean isDefaultSelected() {
         return defaultEncodingButton.getSelection();
     }

     /**
      * Set the title of the group to groupTitle. If this is not called a default
      * title is used. If groupTitle is <code>null</code> the control will be
      * unlabelled (by default a {@link Composite} instead of a {@link Group}.
      *
      * <strong>NOTE</strong> this value must be set before
      * {@link #createControl(Composite)} is called or it will be ignored.
      *
      * @param groupTitle
      * The groupTitle to set.
      * @since 3.3
      */
     public void setGroupTitle(String groupTitle) {
         this.groupTitle = groupTitle;
     }

 }

